"""
文件操作工具类，提供文件读取、转码、转移、复制、删除等操作
"""

import os
import shutil
from typing import Union, Optional, List, Tuple

try:
    import chardet
    CHARDET_AVAILABLE = True
except ImportError:
    CHARDET_AVAILABLE = False
    print("警告: chardet库未安装，自动编码检测功能将不可用。请运行: pip install chardet")


class FileUtils:
    """
    文件操作工具类，提供各种文件处理功能
    """
    
    @staticmethod
    def read_file(file_path: str, encoding: str = 'utf-8') -> str:
        """
        指定编码读取文件内容，返回全部内容

        :param file_path: 文件路径
        :param encoding: 文件编码，默认为utf-8
        :return: 文件全部内容
        """
        with open(file_path, 'r', encoding=encoding) as file:
            return file.read()
    
    @staticmethod
    def read_file_with_auto_encoding(file_path: str) -> Tuple[str, str]:
        """
        自动检测编码并读取文件内容

        :param file_path: 文件路径
        :return: (文件内容, 检测到的编码)
        """
        if not CHARDET_AVAILABLE:
            raise Exception("chardet库不可用，请安装chardet库")
        
        # 先以二进制方式读取文件
        with open(file_path, 'rb') as file:
            raw_data = file.read()
        
        # 检测编码
        detected_encoding = chardet.detect(raw_data)['encoding']
        
        # 使用检测到的编码读取文件
        content = raw_data.decode(detected_encoding)
        
        return content, detected_encoding
    
    @staticmethod
    def write_file(file_path: str, content: str, encoding: str = 'utf-8') -> None:
        """
        指定编码写入文件内容

        :param file_path: 文件路径
        :param content: 文件内容
        :param encoding: 文件编码，默认为utf-8
        """
        with open(file_path, 'w', encoding=encoding) as file:
            file.write(content)
    
    @staticmethod
    def convert_encoding(file_path: str, from_encoding: str, to_encoding: str, 
                        output_path: Optional[str] = None) -> None:
        """
        将文件内容进行转码

        :param file_path: 源文件路径
        :param from_encoding: 源文件编码
        :param to_encoding: 目标编码
        :param output_path: 输出文件路径，如果为None则覆盖原文件
        """
        # 读取原文件内容
        content = FileUtils.read_file(file_path, from_encoding)
        
        # 确定输出路径
        if output_path is None:
            output_path = file_path
            
        # 写入新编码的文件
        FileUtils.write_file(output_path, content, to_encoding)
    
    @staticmethod
    def convert_encoding_auto(file_path: str, to_encoding: str, 
                             output_path: Optional[str] = None) -> str:
        """
        自动检测源文件编码并转换为目标编码

        :param file_path: 源文件路径
        :param to_encoding: 目标编码
        :param output_path: 输出文件路径，如果为None则覆盖原文件
        :return: 检测到的源文件编码
        """
        if not CHARDET_AVAILABLE:
            raise Exception("chardet库不可用，请安装chardet库")
        
        # 自动检测编码并读取文件
        content, detected_encoding = FileUtils.read_file_with_auto_encoding(file_path)
        
        # 确定输出路径
        if output_path is None:
            output_path = file_path
            
        # 写入新编码的文件
        FileUtils.write_file(output_path, content, to_encoding)
        
        return detected_encoding
    
    @staticmethod
    def copy_file(src_path: str, dst_path: str, overwrite: bool = True) -> bool:
        """
        复制文件

        :param src_path: 源文件路径
        :param dst_path: 目标文件路径
        :param overwrite: 是否覆盖已存在的文件
        :return: 复制是否成功
        """
        try:
            # 检查源文件是否存在
            if not os.path.exists(src_path):
                raise FileNotFoundError(f"源文件不存在: {src_path}")
            
            # 检查目标文件是否存在
            if os.path.exists(dst_path) and not overwrite:
                raise FileExistsError(f"目标文件已存在且不允许覆盖: {dst_path}")
            
            # 复制文件
            shutil.copy2(src_path, dst_path)
            return True
        except Exception as e:
            print(f"复制文件失败: {e}")
            return False
    
    @staticmethod
    def move_file(src_path: str, dst_path: str, overwrite: bool = True) -> bool:
        """
        移动文件

        :param src_path: 源文件路径
        :param dst_path: 目标文件路径
        :param overwrite: 是否覆盖已存在的文件
        :return: 移动是否成功
        """
        try:
            # 检查源文件是否存在
            if not os.path.exists(src_path):
                raise FileNotFoundError(f"源文件不存在: {src_path}")
            
            # 检查目标文件是否存在
            if os.path.exists(dst_path) and not overwrite:
                raise FileExistsError(f"目标文件已存在且不允许覆盖: {dst_path}")
            
            # 移动文件
            shutil.move(src_path, dst_path)
            return True
        except Exception as e:
            print(f"移动文件失败: {e}")
            return False
    
    @staticmethod
    def delete_file(file_path: str) -> bool:
        """
        删除文件

        :param file_path: 文件路径
        :return: 删除是否成功
        """
        try:
            if os.path.exists(file_path):
                os.remove(file_path)
                return True
            else:
                print(f"文件不存在: {file_path}")
                return False
        except Exception as e:
            print(f"删除文件失败: {e}")
            return False
    
    @staticmethod
    def search_string_in_file(file_path: str, search_string: str, 
                             encoding: str = 'utf-8') -> int:
        """
        查找某个字段在文件中出现的次数

        :param file_path: 文件路径
        :param search_string: 要查找的字符串
        :param encoding: 文件编码
        :return: 出现次数
        """
        try:
            content = FileUtils.read_file(file_path, encoding)
            return content.count(search_string)
        except Exception as e:
            print(f"搜索文件内容失败: {e}")
            return 0
    
    @staticmethod
    def search_string_in_file_lines(file_path: str, search_string: str, 
                                   encoding: str = 'utf-8') -> List[int]:
        """
        查找某个字段在文件各行中出现的行号

        :param file_path: 文件路径
        :param search_string: 要查找的字符串
        :param encoding: 文件编码
        :return: 包含该字符串的行号列表（从1开始计数）
        """
        try:
            line_numbers = []
            with open(file_path, 'r', encoding=encoding) as file:
                for line_num, line in enumerate(file, 1):
                    if search_string in line:
                        line_numbers.append(line_num)
            return line_numbers
        except Exception as e:
            print(f"搜索文件内容失败: {e}")
            return []
    
    @staticmethod
    def get_file_info(file_path: str) -> dict:
        """
        获取文件信息

        :param file_path: 文件路径
        :return: 文件信息字典
        """
        try:
            stat = os.stat(file_path)
            return {
                'size': stat.st_size,
                'created_time': stat.st_ctime,
                'modified_time': stat.st_mtime,
                'accessed_time': stat.st_atime,
                'is_file': os.path.isfile(file_path),
                'is_directory': os.path.isdir(file_path),
                'absolute_path': os.path.abspath(file_path),
                'basename': os.path.basename(file_path),
                'dirname': os.path.dirname(file_path)
            }
        except Exception as e:
            print(f"获取文件信息失败: {e}")
            return {}
    
    @staticmethod
    def create_directory(dir_path: str, parents: bool = True) -> bool:
        """
        创建目录

        :param dir_path: 目录路径
        :param parents: 是否创建父目录
        :return: 创建是否成功
        """
        try:
            if parents:
                os.makedirs(dir_path, exist_ok=True)
            else:
                os.mkdir(dir_path)
            return True
        except Exception as e:
            print(f"创建目录失败: {e}")
            return False
    
    @staticmethod
    def list_files(dir_path: str, extension: Optional[str] = None, 
                  recursive: bool = False) -> List[str]:
        """
        列出目录中的文件

        :param dir_path: 目录路径
        :param extension: 文件扩展名过滤（如 '.txt'），默认不过滤
        :param recursive: 是否递归搜索子目录
        :return: 文件路径列表
        """
        try:
            file_list = []
            
            if recursive:
                for root, dirs, files in os.walk(dir_path):
                    for file in files:
                        if extension is None or file.endswith(extension):
                            file_list.append(os.path.join(root, file))
            else:
                for item in os.listdir(dir_path):
                    item_path = os.path.join(dir_path, item)
                    if os.path.isfile(item_path):
                        if extension is None or item.endswith(extension):
                            file_list.append(item_path)
            
            return file_list
        except Exception as e:
            print(f"列出文件失败: {e}")
            return []
    
    @staticmethod
    def file_exists(file_path: str) -> bool:
        """
        检查文件是否存在

        :param file_path: 文件路径
        :return: 文件是否存在
        """
        return os.path.exists(file_path) and os.path.isfile(file_path)
    
    @staticmethod
    def directory_exists(dir_path: str) -> bool:
        """
        检查目录是否存在

        :param dir_path: 目录路径
        :return: 目录是否存在
        """
        return os.path.exists(dir_path) and os.path.isdir(dir_path)
    
    @staticmethod
    def get_file_size(file_path: str) -> int:
        """
        获取文件大小（字节）

        :param file_path: 文件路径
        :return: 文件大小（字节）
        """
        try:
            return os.path.getsize(file_path)
        except Exception as e:
            print(f"获取文件大小失败: {e}")
            return -1